#  This is part of the Scopia data visualization example collection
#
#
#  Copyright (C) 2008  Scopia Visual Interfaces Systems 
#                      http://www.scopia.es
#
#  author: Alejandro Conty
#  contributors:
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
from numpy import *

# A transaction is ( idx, idfrom, idto, amount, time )
V_INDEX = 0
V_FROM = 1
V_TO = 2
V_TYPE = 3
V_AMOUNT = 4
V_TIME = 5

class TransactionTree(object):
    def __init__(self, transactions):
        self.root = self.buildBranch( transactions )

    def buildBranch(self, transactions):
        if len(transactions) == 1:
            return (None, transactions[0])
        else:
            cutpoint = len(transactions) / 2
            tcut = transactions[cutpoint][V_TIME]
            left = self.buildBranch( transactions[:cutpoint] )
            right = self.buildBranch( transactions[cutpoint:] )
            return ( tcut, left, right )

    def search( self, tmin, tmax ):
        found = []
        self._search( found, self.root, tmin, tmax )
        return found

    def _search(self, found, node, tmin, tmax ):
        if node[0] is None:
            if node[1][V_TIME] >= tmin and node[1][V_TIME] <= tmax:
                found.append( node[1] )
        else:
            if tmin < node[0]: self._search( found, node[1], tmin, tmax )
            if tmax >= node[0]: self._search( found, node[2], tmin, tmax )

    def getNearest(self, t):
        found = []
        v = self.getLeft( self.root, t)
        if v: found.append(v)
        v = self.getRight( self.root, t)
        if v: found.append(v)
        return found

    def getLeft(self, node, t):
        if node[0] is None: 
            if node[1][V_TIME] < t: return node[1]
            else: return None
        else:
            if node[0] < t:
                right = self.getLeft( node[2], t )
                if right: return right
                else: return self.getLeft( node[1], t )
            else: return self.getLeft( node[1], t )
    
    def getRight(self, node, t):
        if node[0] is None: 
            if node[1][V_TIME] >= t: return node[1]
            else: return None
        else:
            if node[0] >= t:
                left = self.getRight( node[1], t )
                if left: return left
                else: return self.getRight( node[2], t )
            else: return self.getRight( node[2], t )
            

import copy

class Chart(object):
    def __init__(self, transactions):
        self.players = set([ v[V_FROM] for v in transactions ] + [ v[V_TO] for v in transactions ])
        aws = [ (players[a].volume, a) for a in self.players ]
        aws.sort()
        aws2 = []
        for t in aws:
            if len(aws2) % 2: aws2 = [t] + aws2
            else: aws2.append(t)
        self.players = [ t[1] for t in aws2 ]
        self.initialstatus = [ 0 ] * len(self.players)
        self.vtree = TransactionTree( transactions )
        self.transactions = transactions
        self.evolution = [ [0] * len(self.players) ]
        self.indices = {}
        for i, id in enumerate(self.players):
            self.indices[ id ] = i

        for idx, vfrom, vto, tipo, amount, t in self.transactions:
            status = copy.copy(self.evolution[-1])
            status[ self.indices[vfrom] ] -= amount
            status[ self.indices[vto] ] += amount
            self.evolution.append( status )
        
        self.deltas = []
        for idx, vfrom, vto, tipo, amount, t in self.transactions:
            delta = zeros( (len(self.players),), 'f')
            delta[ self.indices[vfrom] ] = -amount
            delta[ self.indices[vto] ] = amount
            self.deltas.append( delta )
        self.evolution = array( self.evolution[1:], 'f' )
        self.deltas = array( self.deltas, 'f' )
        self.maxdelta = max(sum(abs(self.deltas), axis=0)) / (self.transactions[-1][V_TIME] - self.transactions[0][V_TIME])
        self.times = array( [v[V_TIME] for v in self.transactions ], 'f' )

    def getWindow(self, tmin, tmax):
        transactions = self.vtree.search( tmin, tmax )
        if transactions:
            start, end = transactions[0][V_INDEX], transactions[-1][V_INDEX] + 1
            times = self.times[start:end]
            statuses = self.evolution[start:end]
            deltas = self.deltas[start:end]
            center = (tmin + tmax) * 0.5
            radius = tmax - center
            W = abs((times - center)*(1.0/radius))
            W = 1.0 - W*W
            W = W.clip(0, 1)
            #W[ W < 0 ] = 0
            if len(transactions) == 1: status = sum( statuses, axis=0)
            else: status = sum( statuses * W[:,newaxis, ...] * 1.0 / sum(W), axis=0)
            delta = sum( abs(deltas) * W[:,newaxis, ...] * (1.0/(tmax-tmin)) * (1.0/self.maxdelta), axis=0)
            deltadir = sum( deltas * W[:,newaxis, ...] * (1.0/(tmax-tmin)) * (1.0/self.maxdelta), axis=0)
        else: 
            W = []
            v = self.vtree.getNearest( (tmin + tmax) * 0.5 ) [0]
            status = self.evolution[ v[V_INDEX] ]
            delta = abs(self.deltas[ v[V_INDEX] ]) * (1.0/(tmax-tmin)) * (1.0/self.maxdelta)
            deltadir = self.deltas[ v[V_INDEX] ] * (1.0/(tmax-tmin)) * (1.0/self.maxdelta)
        nz = delta.nonzero()
        deltadir[nz] /= delta[nz]
        deltadir *= 0.5
        deltadir += 0.5
        delta = delta.clip(0, 1)
        #delta[delta>1] = 1.0
        return (transactions, status, delta, deltadir, W)

import csv
csv.excel.skipinitialspace = True
from datetime import datetime

class Player(object):
    def __init__(self, idx, name, vol):
        self.index = idx
        self.name = name
        self.volume = vol

def loadPlayers(filename='players.csv'):
    f = open(filename, 'rb')
    reader = csv.reader( f, csv.excel )
    names = None
    players = {}
    for row in reader:
        if names is None: names = row
        else:
            D = dict(zip( names, row ))
            player = Player( int(D['index']), D['name'], float(D['volume']) )
            players[ player.index ] = player
    return players

def loadTransactions(filename):
    f = open(filename, 'rb')
    reader = csv.reader( f, csv.excel )
    names = None
    transactions = []
    for row in reader:
        if names is None: names = row
        else:
            D = dict(zip( names, row ))
            transaction = [ 0, int(D['idfrom']), int(D['idto']), D['type'], int(D['amount']), 
                      datetime( int(D['year']), int(D['month']), int(D['day']), int(D['hour']), int(D['minute']), int(D['second']) ) ]
            transactions.append( transaction )
    transactions.sort(key=lambda v: v[V_TIME])
    starttime = transactions[0][V_TIME]
    for i,v in enumerate(transactions): 
        v[V_INDEX] = i
        delta = v[V_TIME] - starttime
        v[V_TIME] = ( delta.days*24*3600 + delta.seconds )
    return [tuple(v) for v in transactions], starttime


players = loadPlayers('players.csv')
